// programa client cujo objetivo e solicitar um arquivo ao servidor e gavar-lo em disco local
//@param host_do_servidor porto_servidor nome_arquivo tam_buffer
//@host_do_servidor = nome do servidor ex(localhost,127.0.0.1)
//@porto_servidor = porto em que o servidor esta "bind"
//@nome_arquivo = nome do arquivo a ser solicitado
//@tam_buffer = tamanho do buffer a ser utilizado
//@tam_janela= tamanho da janela a ser utilizado

//bibliotecas utilizadas
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include "protocolo.h"
#include "tp_socket.h"
#define MTU 4096 

//funcao de auxilio para informar erros e abortar
void error(char *msg)
{
	printf("\n%s\n", msg);
	exit(0);
}
//funcao de auxilio para informar como usar o programa
void usage()
{
	printf("Usage: \n ./client host_server port nome_arquivo tam_buffer tam_janela\n"); 
	printf("\t host_server: \t  Nome do servidor\n"); 
	printf("\t port: \t\t  Porto do servidor\n"); 
	printf("\t nome_arquivo:\t  Arquivo a ser requerido\n"); 
	printf("\t tam_buffer:\t  Tamanho do buffer a ser utilizado\n"); 
	printf("\t tam_janela:\t  Tamanho da janela a ser utilizado\n"); 
} 

int main(int argc, char *argv[])
{
	//verifica numero de argumentos
	if (argc != 6) {
		usage();	
		error("Numero de argumentos invalido");	 
	}

	


	//declaracoes iniciais 
	int sockfd, port=atoi(argv[2]), retorno, totalBytes=0, totalMsg=0, tam_janela=atoi(argv[5]);
	char *arquivo = argv[3];
	struct sockaddr_in serv_addr;
	struct hostent *server;
	int buffer_size = atoi(argv[4]);
	int tamanho_total_pacote= buffer_size  + TAMANHO_CABECALHO_COM_CALDA;
	char buffer[tamanho_total_pacote];
	char *Msg;
	char *ack;
	struct timeval time1, time2;
	FILE *file;
	int sec, msec; 
	float kbps;
	Pacote ACK,pacoteSend,pacoteRecv;

	//verifica se o buffer pedido é coerente
	if (buffer_size >= (MTU - TAMANHO_CABECALHO_COM_CALDA)){

		error("Este buffer nao pode ser passado neste canal, ele é maior que o MTU");
	}else if( buffer_size < (strlen(arquivo) + TAMANHO_CABECALHO_COM_CALDA)){

		error("Não cabe o nome do arquivo em um pacote");
	}



	//inicia a contagem do tempo
	gettimeofday(&time1,NULL);


	//cria o novo socket TCP
	sockfd = tp_socket(0);

	if (sockfd < 0){ 
		error("ERROR abrindo o socket");
	}

	if ( tp_build_addr(&serv_addr,argv[1],port) < 0){
		error("Problema criando sockaddir client");
	}

	//abre o arquivo para ser escrito
	file = fopen(arquivo,"w+");

	if ( file == NULL ){
		error("Problema ao abrir o arquivo para escrita");
	}


	//seta timeout Pro ack
	struct timeval tv;
	tv.tv_sec  = 1;  
	tv.tv_usec = 0;
	setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

	//monta pacote com nome do arquivo
	empacota(&pacoteSend,'0',0,strlen(arquivo),arquivo);
	Msg = pacoteToString(&pacoteSend);
	do{
		//envia nome do arquivo
		tp_sendto(sockfd,Msg, strlen(Msg), &serv_addr);		

		//recebe a confirmacao do pedido - de maneira stop-and-wait
		retorno = tp_recvfrom(sockfd,buffer,tamanho_total_pacote,&serv_addr); 
		buffer[retorno]='\0';

		if(retorno > 0){/* retornou algum pacote(ack)?*/
			stringToPacote(&pacoteRecv,buffer,retorno);
			//Verifica checksum e se é um pacote ACK
			if((check(&pacoteRecv)) && (getAck(&pacoteRecv) == '0')){
				break;
			}
		}
	}while(1);

	//começa a receber o arquivo
	//zera o buffer
	bzero(buffer,sizeof(buffer));
	//Inicia recebimento do arquivo
	int pacoteEsperado=0;

	do{

		// if(retorno < 0) break;

		if ( retorno > 0) {

			//verifica se é pacote sinalizando final
			if (getAck(&pacoteRecv) == 'f'){
				break;
			}


			//printf("Chegou: %d == %d \n\n", getNPacote(&pacoteRecv), pacoteEsperado);
			if(check(&pacoteRecv) && (getNPacote(&pacoteRecv) == pacoteEsperado) ){

				//escreve no arquivo 
				fwrite(getConteudo(&pacoteRecv),1,getConteudoSize(&pacoteRecv),file);

				//envia ACK do pacote recebido
				empacota(&ACK,'1',getNPacote(&pacoteRecv),0,"");
				ack = pacoteToString(&ACK);

				tp_sendto(sockfd,ack,strlen(ack),&serv_addr);
				pacoteEsperado++;

			}else{
				if(getNPacote(&pacoteRecv) < pacoteEsperado){

					//envia ACK do pacote recebido
					empacota(&ACK,'1',getNPacote(&pacoteRecv),0,"");
					ack = pacoteToString(&ACK);

					tp_sendto(sockfd,ack,strlen(ack),&serv_addr);

				}
			}
			totalBytes+=retorno;
			totalMsg++;
		}
		retorno = tp_recvfrom(sockfd,buffer,tamanho_total_pacote,&serv_addr);
		buffer[retorno]='\0';
		stringToPacote(&pacoteRecv,buffer,retorno);

	}while(1);






	//fecha o arquivo 
	fclose(file);

	//fecha a conexao, para de receber e transmitir
	shutdown(sockfd,2);


	//termina a contagem do tempo
	gettimeofday(&time2,NULL);

	//calcula as medidas com base no tempo gasto
	sec = (int)  (time2.tv_sec - time1.tv_sec);
	msec = (int ) (time2.tv_usec - time1.tv_usec);
	kbps = (totalBytes* 8.0 /( (int)sec + (float) ( msec /1000000.0) )) /1024.0;

	//imprime os resultados
	printf("Iteracoes no recv(): %d\n", totalMsg);
	printf("Buffer = \%5u byte(s), \%10.2f kbps (\%u bytes em \%3u.\%06u s)\n", totalBytes, kbps, totalBytes,(unsigned int) sec, (unsigned int) msec);


}
